﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Application;
using JetBrains.Application.Progress;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Daemon;
using JetBrains.ReSharper.Daemon.Stages;
using JetBrains.ReSharper.Daemon.Stages.Dispatcher;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.LinqTools;
using JetBrains.ReSharper.Intentions.Bulk;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Extensibility.Menu;
using JetBrains.ReSharper.Intentions.QuickFixes.Bulk;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Parsing;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Files;
using JetBrains.ReSharper.Psi.Pointers;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;
using SPCAFContrib.ReSharper.Common;
using SPCAFContrib.ReSharper.Common.Attributes;
using SPCAFContrib.ReSharper.Common.CodeAnalysis;
using SPCAFContrib.ReSharper.Common.Extensions;
using SPCAFContrib.ReSharper.Common.QuickFix;
using SPCAFContrib.ReSharper.Consts;
using SPCAFContrib.ReSharper.Inspection.Code.SPCAF;

[assembly: RegisterConfigurableSeverity(SPC055201Highlighting.CheckId,
  null,
  Consts.SPCAF_BEST_PRACTICES_GROUP,
  SPC055201Highlighting.CheckId + ": " + SPC055201Highlighting.Message,
  "Use SPContentTypeCollection.BestMatch(string) to retrieve a Content Type from a SPContentTypeCollection.",
  Severity.WARNING,
  false, Internal = false)]

namespace SPCAFContrib.ReSharper.Inspection.Code.SPCAF
{
    [ElementProblemAnalyzer(new[] { typeof(IElementAccessExpression) }, HighlightingTypes = new[] { typeof(SPC055201Highlighting) })]
    [Applicability(
        IDEProjectType.SP2010FarmSolution |
        IDEProjectType.SPSandbox |
        IDEProjectType.SP2013FarmSolution |
        IDEProjectType.SPServerAPIReferenced)]
    public class ConsiderBestMatchForContentTypesRetrieval : ElementProblemAnalyzer<IElementAccessExpression>
    {
        protected override void Run(IElementAccessExpression element, ElementProblemAnalyzerData analyzerData,
            IHighlightingConsumer consumer)
        {
            IPsiSourceFile sourceFile = element.GetSourceFile();

            if (sourceFile != null)
            {
                if (sourceFile.HasExcluded(analyzerData.SettingsStore)) return;

                IProject project = sourceFile.GetProject();

                if (project != null)
                {
                    if (project.IsApplicableFor(this))
                    {
                        if (IsInvalid(element)) 
                        {
                            consumer.AddHighlighting(
                                new SPC055201Highlighting(element),
                                element.GetDocumentRange(),
                                element.GetContainingFile());
                        }
                    }
                }
            }
        }

        public static bool IsInvalid(IElementAccessExpression element)
        {
            bool result = false;

            IExpressionType expressionType = element.Operand.GetExpressionType();

            if (expressionType.IsResolved)
            {
                var nextToken = element.Operand.GetNextMeaningfulToken();

                if ((element.Operand.IsResolvedAsPropertyUsage(ClrTypeKeys.SPWeb, new[] { "ContentTypes" }) ||
                    element.Operand.IsResolvedAsPropertyUsage(ClrTypeKeys.SPList, new[] { "ContentTypes" })) &&
                    (nextToken != null && nextToken.GetTokenType() == CSharpTokenType.LBRACKET))
                {
                    TreeNodeCollection<ICSharpArgument> arguments = element.Arguments;
                    ICSharpArgument firstArgument = arguments.First();
                    if (firstArgument.MatchingParameter != null)
                    {
                        var st = firstArgument.MatchingParameter.Element.Type.GetScalarType();
                        if (st != null && st.GetClrName().Equals(ClrTypeKeys.SPContentTypeId))
                        {
                            var methodCriteria = new MethodCriteria() {ShortName = "BestMatch"};
                            bool varInitializationBestMatchExists = false;
                            bool methodHasVarAssigment = false;
                            bool innerBestMatchExists = !firstArgument.Value.IsClassifiedAsVariable &&
                                                        firstArgument.Value.IsResolvedAsMethodCall(
                                                            ClrTypeKeys.SPContentTypeCollection,
                                                            new[] {methodCriteria});

                            if (!innerBestMatchExists && 
                                firstArgument.Value.IsClassifiedAsVariable && 
                                firstArgument.Value is IReferenceExpression)
                            {
                                var referenceExpression = (firstArgument.Value as IReferenceExpression);
                                var resolveInfo = referenceExpression.Reference.Resolve();
                                if (resolveInfo.DeclaredElement != null &&
                                    resolveInfo.ResolveErrorType == ResolveErrorType.OK &&
                                    resolveInfo.DeclaredElement is ILocalVariableDeclaration)
                                {
                                    var var_decl = (resolveInfo.DeclaredElement as ILocalVariableDeclaration);

                                    if (var_decl.Initializer != null && var_decl.Initializer is IExpressionInitializer)
                                    {
                                        varInitializationBestMatchExists =
                                            (var_decl.Initializer as IExpressionInitializer).Value
                                                .IsResolvedAsMethodCall(ClrTypeKeys.SPContentTypeCollection,
                                                    new[] {methodCriteria});
                                    }
                                }

                                if (!varInitializationBestMatchExists)
                                {
                                    ICSharpTypeMemberDeclaration method = element.GetContainingTypeMemberDeclaration();
                                    methodHasVarAssigment =
                                        method.HasVarAssigmentWithMethodUsage(
                                            referenceExpression.NameIdentifier.Name,
                                            ClrTypeKeys.SPContentTypeCollection, new[] {methodCriteria});
                                }
                            }
                            
                            result = !innerBestMatchExists && !varInitializationBestMatchExists && !methodHasVarAssigment;
                        }
                    }
                }
            }

            return result;
        }
    }

    [ConfigurableSeverityHighlighting(CheckId, CSharpLanguage.Name, OverlapResolve = OverlapResolveKind.NONE, ShowToolTipInStatusBar = true)]
    public class SPC055201Highlighting : IHighlighting
    {
        public const string CheckId = "SPC055201";
        public const string Message = "Consider using SPContentTypeCollection.BestMatch(SPContentTypeId) to retrieve a Content Type";

        public IElementAccessExpression Element { get; private set; }

        public SPC055201Highlighting(IElementAccessExpression element)
        {
            this.Element = element;
        }

        #region IHighlighting Members

        public string ToolTip
        {
            get
            {
                return String.Format("{0}: {1}", CheckId, Message);
            }
        }
        public string ErrorStripeToolTip
        {
            get { return ToolTip; }
        }

        public int NavigationOffsetPatch
        {
            get { return 0; }
        }

        public bool IsValid()
        {
            return this.Element != null && this.Element.IsValid();
        }

        #endregion
    }
}
